﻿<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        html, body {
            height:100%;
        }
        body {
            background:#333;
            overflow:hidden;
        }
        canvas {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>

<body>
    <canvas width="600" height="600"></canvas>
    <script>
        /* -----------------------  坐标系封装  ----------------------- */

        function Coord(option) {
            if (this === window) {
                return new Coord(option);
            }

            //默认坐标系值
            this.cas = option.cas;
            this.ctx = this.cas.getContext('2d');
            this.padding = option.padding || 40;
            this.x0 = this.padding;
            this.y0 = this.cas.height - this.padding;
            this.width = this.cas.width - 2 * this.padding;
            this.height = this.cas.height - 2 * this.padding;
            //箭头样式大小    默认宽度14  高度20
            this.arrowWidth = option.arrowWidth || 14;
            this.arrowHeight = option.arrowHeight || 20;
            //初始比例
            this.scaleX = this.width / 110;
            this.scaleY = this.height / 110;

            //刻度
            //刻度样式
            this.textStyle = option.textStyle || 'palevioletred';
            this.textFont = option.textFont || '15px Consoles';
            this.distance = option.distance || 10;
            
            //内部图形存储
            this.graph = {};
            //坐标系样式
            //线样式
            this.strokeStyle = option.strokeStyle || 'dodgerblue';
            //填充样式
            this.fillStyle = option.fillStyle || 'seagreen';

            //如果要坐标刻度  默认有   如果设置了grad属性  就使用grad属性值  否则设置为true
            this.hasGrad = 'grad' in option ? option.grad : true;
            if (this.hasGrad) {
                this.gradX = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
                this.gradY = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
                this.grad();
            }
        }
        Coord.prototype = {
            constructor: Coord,
            //画出本坐标系
            stroke: function () {
                //参数
                var cas = this.cas,
                    ctx = this.ctx,
                    padding = this.padding,
                    x0 = this.x0,
                    y0 = this.y0,
                    arrowWidth = this.arrowWidth,
                    arrowHeight = this.arrowHeight;

                ctx.beginPath();

                //绘制坐标系线
                ctx.moveTo(x0, y0);
                ctx.lineTo(x0, y0 - this.height);
                ctx.moveTo(x0, y0);
                ctx.lineTo(x0 + this.width, y0);
                //添加样式样式
                ctx.strokeStyle = this.strokeStyle;
                ctx.stroke();

                //绘制箭头
                //Y轴
                ctx.moveTo(x0, y0 - this.height - 4);
                ctx.lineTo(x0 - arrowWidth * .5, y0 - this.height + arrowHeight);
                ctx.lineTo(x0, y0 - this.height + arrowHeight * .5);
                ctx.lineTo(x0 + arrowWidth * .5, y0 - this.height + arrowHeight);
                ctx.closePath();
                //X轴
                ctx.moveTo(x0 + this.width + 4 , y0);
                ctx.lineTo(x0 + this.width - arrowHeight, y0 - arrowWidth * .5);
                ctx.lineTo(x0 + this.width - arrowHeight * .5, y0);
                ctx.lineTo(x0 + this.width - arrowHeight, y0 + arrowWidth * .5);
                ctx.closePath();

                //如果有样式
                if (this.fillStyle) ctx.fillStyle = this.fillStyle;
                ctx.fill();
                //返回
                return this;
            },
            //使所有该坐标系中的点连接起来
            linePointAll: function (style) {
                //如果当前坐标系点数组存在   并且点个数大于1
                if (this.graph.pointArr && this.graph.pointArr.length > 1) {
                    var points = this.graph.pointArr,
                        i = 0;
                    while (points[i + 1]) {                      //如果下一项存在
                        points[i].lineTo(points[i += 1], style); //从当前项连接到下一项  使用设置的样式
                    }
                    return this;
                } else return this;
            },
            //生成坐标
            grad: function () {
                var ctx = this.ctx,
                    x0 = this.x0,
                    y0 = this.y0,
                    gradX = this.gradX,
                    gradY = this.gradY,
                    //计算间隔
                    stepX = this.width / (gradX.length + 1),
                    stepY = this.height / (gradY.length + 1),
                    i = 0;
                //绘制坐标刻度
                //设置样式
                ctx.beginPath();
                //样式记录
                ctx.save();

                //线样式
                ctx.strokeStyle = this.strokeStyle;
                //文体样式
                ctx.font = this.textFont;
                ctx.fillStyle = this.textStyle;

                //x轴
                ctx.textAlign = 'center';
                ctx.textBaseline = 'top';
                for (; i < gradX.length; i++) {
                    ctx.moveTo(x0 + (i + 1) * stepX, y0 - 5);
                    ctx.lineTo(x0 + (i + 1) * stepX, y0 + 5);
                    //文字
                    ctx.fillText(gradX[i], x0 + (i + 1) * stepX, y0 + this.distance);
                }
                //y轴
                ctx.textAlign = 'right';
                ctx.textBaseline = 'middle';
                for (i = 0; i < gradY.length; i++) {
                    ctx.moveTo(x0 + 5, y0 - (i + 1) * stepY);
                    ctx.lineTo(x0 - 5, y0 - (i + 1) * stepY);
                    //文字
                    ctx.fillText(gradY[i], x0 - this.distance, y0 - (i + 1) * stepY);
                }
                ctx.stroke();
                //样式恢复
                ctx.restore();
            },
            //重新生成坐标
            reGrad: function () {
                if(!this.graph.pointArr){
                    //抹除画布  所有更新
                    cas.width = cas.width;
                    //坐标系
                    this.stroke();
                    this.grad();
                    return;
                }
                if (this.graph.pointArr.length < 1) {
                    //如果只有一个点
                    this.graph.pointArr[0].stroke();
                    return;
                }
                //自身坐标中的x和y值数组
                var dataY = [],
                    i = 0,
                    pointArr = this.graph.pointArr,
                    degX,
                    degY,
                    xStep,
                    yStep;
                for (; i < pointArr.length; i++) {
                    dataY.push(pointArr[i].y);
                }
                dataY.sort(function (a, b) {
                    return b - a;
                });
                //坐标组重生成
                //X坐标
                degX = pointArr[pointArr.length - 1].x > 10 ? 100 : 10;
                xStep = Math.ceil(pointArr[pointArr.length - 1].x / degX) * degX / 10;
                //Y坐标
                degY = dataY[0] > 10 ? 100 : 10;
                yStep = Math.ceil(dataY[0] / degY) * degY / 10;
                this.gradX = this.gradX.map(function (v, i) {
                    return (i + 1) * xStep;
                });
                this.gradY = this.gradY.map(function (v, i) {
                    return (i + 1) * yStep;
                });
                //比例更新
                this.scaleX = this.width / xStep / 11;
                this.scaleY = this.height / yStep / 11;
                //抹除画布  所有更新
                cas.width = cas.width;
                //坐标系
                this.stroke();
                this.grad();

                //点
                this.graph.pointArr.forEach(function (v, i) {
                    v.stroke();
                });
            }
        };

        /* ----------------------   点绘制的封装  ---------------------- */
        function Point(option) {
            //如果是奇数数组  不符合要求
            if (option.data.length % 2 !== 0) return;
            //对传入data数组处理为2位数组     [x, y]
            //  把option 的内容都处理为单个的点  递归调用本函数
            if (option.data.length > 2) {
                //克隆当前的配置
                var temp = Clone(option);
                //截取内容前两个
                temp.data = option.data.splice(0, 2);
                //前两个执行画点
                Point(temp);
                //后续的内容继续进行截取
                Point(option);
                return;
            }

            if (this === window) return new Point(option);

            //参数生成
            this.cas = option.coord.cas;
            this.ctx = option.coord.cas.getContext('2d');
            this.coord = option.coord;

            //坐标处理
            this.x = option.data[0];
            this.y = option.data[1];

            this.radius = option.radius || 4;
            //样式处理
            this.style = option.style || 'arc';
            //绘图方式判定
            //如果是线型点    （中空）
            if (option.strokeStyle) this.strokeStyle = option.strokeStyle;
            //如果没有设置线型则为填充   让其填充
            else this.fillStyle = option.fillStyle || '#ff6666' ;
            //在坐标系中记录该点
            if (this.coord.graph.pointArr) {
                //判断放入的位置       在放入时就排好序列         要进行处理数组位置  否则在连线时会回折
                var pointArr = this.coord.graph.pointArr,
                    i = 0;
                //如果比第一个小 直接放在第一个前面
                if (this.x <= pointArr[0].x) pointArr.unshift(this);
                else {
                    //每次判断下一项是否存在  如果存在  和下一项比较,如果比下一项小 跳出， i = 下一项
                    //                        如果不存在  i = arr.length
                    while (pointArr[++i]) {
                        if (this.x <= pointArr[i].x) break;
                    }
                    //把当前点放在i对应的下标之前
                    pointArr.splice(i, 0, this);
                }
                //如果在坐标系中还没有点数组   就生成点数组
            } else this.coord.graph.pointArr = [this];

            //每次放入一个点  根据点的坐标值绘制坐标系
            coord1.reGrad();
            //每个直接生成在图上   可直接用lineTo进行设置连接到对应的点
            this.stroke();

            
             //定义克隆函数  用于递归生成单独点
            function Clone(option) {
                if (this === window) return new Clone(option);
                for (var k in option) {
                    this[k] = option[k];
                }
            }
        }
        //点公共方法
        Point.prototype = {
            constructor: Point,
            //渲染点
            stroke: function () {
                //参数
                var cas = this.cas,
                    ctx = this.ctx,
                    radius = this.radius,
                    style = this.style;

                //比例处理
                this.xAb = this.x * this.coord.scaleX;
                this.yAb = this.y * this.coord.scaleY;
                //坐标系转换处理
                this.xAb = this.coord.x0 + this.xAb;
                this.yAb = this.coord.y0 - this.yAb;
                var xAb = this.xAb,
                    yAb = this.yAb;
                //绘制
                ctx.beginPath();
                //判断绘图方式
                if (this.strokeStyle) ctx.strokeStyle = this.strokeStyle;
                else ctx.fillStyle = this.fillStyle;
                //如果是矩形点
                if (style === 'rect') {
                    ctx.moveTo(xAb - radius * .5, yAb - radius * .5);
                    ctx.lineTo(xAb + radius * .5, yAb - radius * .5);
                    ctx.lineTo(xAb + radius * .5, yAb + radius * .5);
                    ctx.lineTo(xAb - radius * .5, yAb + radius * .5);
                } else {
                    //圆形点   //要先移动到对应的起始点
                    ctx.moveTo(xAb + radius , yAb);
                    ctx.arc(xAb, yAb, radius, 0, 2 * Math.PI);
                }
                //绘图
                if (this.strokeStyle) ctx.stroke();
                else ctx.fill();

                //返回当前点  用于链式调用
                return this;
            },
            //从当前点链接到下指定点
            lineTo: function (point, style) {
                var ctx = this.ctx;
                ctx.beginPath();
                ctx.save();
                //默认样式
                ctx.strokeStyle = 'dodgerblue';


                //如果设置style样式
                if (style) {
                    //线色
                    ctx.strokeStyle = style.strokeStyle || 'dodgerblue';
                    //线宽
                    ctx.lineWidth = style.lineWidth;
                }
                ctx.moveTo(this.xAb, this.yAb);
                ctx.lineTo(point.xAb, point.yAb);
                ctx.stroke();
                ctx.restore();
                //返回连接的那个点
                return point;
            }
        };

        /* ----------------   使用说明  ---------------- */
        /*
            使用思路：
            1、建立一个坐标系：   Coord(option);   
                @param   cas                ------  对应的 canvas对象           （必有）

                @param   padding            ------  对canvas的离开距离           默认40
                @param   arrowHeight        ------  坐标轴箭头的高度             默认20
                @param   arrowWidth         ------  坐标轴箭头的宽度             默认14
                @param   strokeStyle        ------  设置坐标线的样式             默认black
                @param   fillStyle          ------  设置箭头的填充样式           默认black
                @param   textStyle          ------  设置刻度文字的样式           默认palevioletred   
                @param   textFont           ------  设置文字的大小和字体样式      默认 15px Consoles
                @param   grad               ------  设置坐标刻度                 grad:false 取消坐标刻度

                方法:
                        .stroke             在canvas中生成该坐标轴   
                        .linePointAll     linePointAll(style)      把本坐标系中的点按x轴依次连接起来
                                参数  style  ------   {}    可选
                                    属性  strokeStyle       线样式     默认为  'dodgerblue'
                                          lineWidth         线宽       默认为 1
                        .reGard           重新刷新坐标
                
                属性：   .graph.pointArr   指定对应的 安装x坐标排序的点数组


            2、绘制点：     Point(option);
                @param   coord              ------  对应的坐标系                  （必有）
                @param   data               ------  点的x，y   以数组形式设置  [x, y]
                                                            或以多个点同时设置[x1, y1, x2, y2]

                @param   style              ------  点的类型                     'arc'(默认)  或 'rect'
                @param   radius             ------  点的半径                    （默认为4）
                @param   fillStyle          ------  指定点为填充点（默认）         样式默认为  '#ff6666'
                @param   strokeStyle        ------  指定点为中空点

                方法：
                        .lineTo(point, style)
                            @param   point   ------  指代要连接的点         在同一个canvas中的点  （必有）

                            @param   style   ------  连接线的样式   {}
                                    属性：  strokeStyle    ------   线色   默认 dodgerblue
                                    属性：  linewidth      ------   线宽   默认 1


             3、使用coord实例对象直接 .linePointAll(style)  可以得到对应的线图;
             4、针对单个的点进行属性的调整    再用  coord1.reGrad(); coord1.linePointAll(); 重新刷新坐标
        */


        /* ---------------------------  示例   --------------------------- */

        
        var cas = document.querySelector('canvas');
        //绘制坐标系
        var coord1 = Coord({
            cas : cas,
            padding: 80,
            textFont: '14px microsoft yahei',
            textStyle: 'white',
            // distance: 15,
            // strokeStyle: 'palevioletred',
            // fillStyle: 'deepskyblue'
        }).stroke();

        //绘制点
        Point({
            coord:coord1,
            data:[100,300, 1500,500, 1888,100, 260,700, 350,600, 420,3000, 480,200],
            fillStyle: 'dodgerblue',
            radius:5
        });
        coord1.linePointAll({
            strokeStyle: 'palegreen'
        });

        //  从特定的点连接到指定的点
        // coord1.graph.pointArr[2].lineTo(coord1.graph.pointArr[4], {
        //     strokeStyle: 'palevioletred'
        // });
        
        //单独对一个点进行样式调整
        coord1.graph.pointArr[1].fillStyle = 'orange';

        coord1.graph.pointArr[2].radius = 10;
        coord1.graph.pointArr[2].fillStyle = 'yellow';

        coord1.graph.pointArr[3].radius = 10;
        coord1.graph.pointArr[3].fillStyle = 'palevioletred';

        coord1.graph.pointArr[4].radius = 20;
        coord1.graph.pointArr[4].style = 'rect';
        coord1.graph.pointArr[4].fillStyle = '#ff6666';


        coord1.graph.pointArr[5].radius = 20;
        coord1.graph.pointArr[5].strokeStyle = 'palevioletred';

        //重新刷新点和坐标系刻度
        coord1.reGrad();
        coord1.linePointAll();




    </script>
</body>

</html>